【AWS CDK】GitHub の Release をトリガーに CodePipeline を起動する(GitHub ソースアクション Version 1 版)
はじめに
テントの中から失礼します、IoT 事業部のてんとタカハシです!
GitHub と CodePipeline を連携した際、デフォルトでは Push をトリガーにして、パイプラインを起動する設定になりますが、ブランチの運用手段によっては Release など他のイベントをトリガーにしたいケースがあります。
今回は、GitHub リポジトリ上での Release をトリガーにして、パイプラインを起動する構成を CDK で構築します。
尚、GitHub のソースアクションは「Version 1」と「Version 2」の2つが存在しますが、本記事では既に非推奨となっている「Version 1」を使用する点についてご注意ください。「Version 2」を使用した場合の構築については別記事にて記載します。
環境
% sw_vers ProductName: macOS ProductVersion: 12.6 BuildVersion: 21G115 % aws --version aws-cli/2.8.2 Python/3.9.11 Darwin/21.6.0 exe/x86_64 prompt/off % cdk --version 2.46.0 (build 5a0595e)
GitHub のアクセストークンを準備する
GitHub のソースアクション「Version 1」を使用して、CodePipeline から GitHub のリポジトリにアクセスするためには、GitHub のアクセストークンが必要です。
https://github.com/settings/tokens にアクセスして「Generate new token (classic)」をクリックします。
アクセストークンの名前を入力した後、repo
と
admin:repo_hook
を許可するためにチェックを入れます。
「Generate token」をクリックして、アクセストークンを生成します。
アクセストークンが表示されますので、コピーしてください。
アクセストークンをソースコードに埋め込むわけにはいかないので、Secrets Manager に登録します。
% aws secretsmanager create-secret \ --name github-sample-token \ --secret-string <GITHUB_ACCESS_TOKEN> { "ARN": "arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:github-sample-token-jvRSBB", "Name": "github-sample-token", "VersionId": "95c08eaa-a640-41b2-a2c7-f5673f65f6a3" }
Push をトリガーにする実装
まずはデフォルトの Push をトリガーにして、パイプラインを起動できるように実装します。
cdk.json
githubOwnerName
、githubRepositoryName
は各自の環境に合わせて変更してください。ここでは、main ブランチに Push したタイミングでパイプラインを起動するようにします。githubTokenName
には、前の手順で Secrets Manager に登録したシークレットの名前を入力しています。
{ "app": "npx ts-node --prefer-ts-exts bin/codepipeline-triggered-by-github-releases.ts", "watch": { ... }, "context": { "projectName": "codepipeline-triggered-by-github-releases", "githubOwnerName": "iam326", "githubRepositoryName": "codepipeline-triggered-by-github-releases", "githubBranchName": "main", "githubTokenName": "github-sample-token", ... } }
bin
cdk.json に埋め込んだ各種情報をスタックに渡しています。
#!/usr/bin/env node import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; import { CodepipelineTriggeredByGithubReleasesStack } from '../lib/codepipeline-triggered-by-github-releases-stack'; const app = new cdk.App(); const projectName = app.node.tryGetContext('projectName'); const githubOwnerName = app.node.tryGetContext('githubOwnerName'); const githubRepositoryName = app.node.tryGetContext('githubRepositoryName'); const githubBranchName = app.node.tryGetContext('githubBranchName'); const githubTokenName = app.node.tryGetContext('githubTokenName'); new CodepipelineTriggeredByGithubReleasesStack(app, `${projectName}-cicd`, { projectName, githubOwnerName, githubRepositoryName, githubBranchName, githubTokenName, });
CICD スタック
GitHub からソースコードをダウンロードする source アクション(GitHub ソースアクション Version 1)と、CodeBuild を実行する deploy アクションを持つパイプラインを作成します。GitHub のアクセストークンは Secrets Manager から取得しています。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as codeBuild from 'aws-cdk-lib/aws-codebuild'; import * as codePipeline from 'aws-cdk-lib/aws-codepipeline'; import * as codePipelineActions from 'aws-cdk-lib/aws-codepipeline-actions'; export interface CodepipelineTriggeredByGithubReleaseStackProps extends cdk.StackProps { projectName: string; githubOwnerName: string; githubRepositoryName: string; githubBranchName: string; githubTokenName: string; } export class CodepipelineTriggeredByGithubReleasesStack extends cdk.Stack { constructor( scope: Construct, id: string, props: CodepipelineTriggeredByGithubReleaseStackProps ) { super(scope, id, props); const { projectName, githubOwnerName, githubRepositoryName, githubBranchName, githubTokenName, } = props; const githubToken = cdk.SecretValue.secretsManager(githubTokenName).unsafeUnwrap(); const sourceArtifact = new codePipeline.Artifact(); const codeBuildDeployProject = new codeBuild.PipelineProject( this, 'CodeBuildDeployProject', { projectName: `${projectName}-deploy-project`, buildSpec: codeBuild.BuildSpec.fromSourceFilename('./buildspec.yml'), } ); const sourceAction = new codePipelineActions.GitHubSourceAction({ actionName: 'source', owner: githubOwnerName, repo: githubRepositoryName, branch: githubBranchName, oauthToken: new cdk.SecretValue(githubToken), output: sourceArtifact, }); const deployAction = new codePipelineActions.CodeBuildAction({ actionName: 'deploy', project: codeBuildDeployProject, input: sourceArtifact, }); const deployPipeline = new codePipeline.Pipeline(this, 'DeployPipeline', { pipelineName: `${projectName}-deploy-pipeline`, stages: [ { stageName: 'source', actions: [sourceAction], }, { stageName: 'deploy', actions: [deployAction], }, ], }); } }
buildspec.yml
CodeBuild が起動すれば良いだけですので、中身は特にありません。
version: 0.2 phases: install: commands: - echo "install ok" build: commands: - echo "build ok"
デモ
下記で CICD スタックをデプロイします。
% cdk deploy "*"
続いて、空コミットを作成してから、main ブランチに Push します。
% git commit --allow-empty -m "cicd test" % git push origin main
すると、パイプラインが起動することを確認できます。
Release をトリガーにする実装
前の手順で作成したリソースを基に、パイプラインを起動するトリガーを Release に変更します。
Webhook 用のシークレットトークンを準備する
GitHub の Release をトリガーにして Webhook で送られてくるペイロードを受付可能なエンドポイントを CodePipeline 側に作成するのですが、その際にシークレットトークンを指定する必要があります。ここでは、ローカルで生成した UUID をシークレットトークンとして扱います。GitHub のアクセストークンと同様に Secrets Manager に登録します。
% aws secretsmanager create-secret \ --name webhook-sample-secret-token \ --secret-string $(uuidgen) { "ARN": "arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:webhook-sample-secret-token-Ve5AE4", "Name": "webhook-sample-secret-token", "VersionId": "47639002-1651-47f6-b64c-fd994bd931f5" }
cdk.json
webhookSecretTokenName
を追加します。前の手順で Secrets Manager に登録したシークレットの名前を入力しています。
... "context": { "projectName": "codepipeline-triggered-by-github-releases", "githubOwnerName": "iam326", "githubRepositoryName": "codepipeline-triggered-by-github-releases", "githubBranchName": "main", "githubTokenName": "github-sample-token", "webhookSecretTokenName": "webhook-sample-secret-token", ...
bin
cdk.json に追加したwebhookSecretTokenName
をスタックに渡すようにします。
... const webhookSecretTokenName = app.node.tryGetContext('webhookSecretTokenName'); new CodepipelineTriggeredByGithubReleasesStack(app, `${projectName}-cicd`, { projectName, githubOwnerName, githubRepositoryName, githubBranchName, githubTokenName, webhookSecretTokenName, });
CICD スタック
デフォルトのトリガーを無効にしつつ、Webhook 用のシークレットトークンを使用して新たな Webhook のエンドポイントを作成します。このエンドポイントでは、GitHub の Release イベントをトリガーとして送られてくるペイロードを通すためのフィルターを設定します。
Release イベントで送られてくるペイロードの例はこちらに記載があります。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as codeBuild from 'aws-cdk-lib/aws-codebuild'; import * as codePipeline from 'aws-cdk-lib/aws-codepipeline'; import * as codePipelineActions from 'aws-cdk-lib/aws-codepipeline-actions'; export interface CodepipelineTriggeredByGithubReleaseStackProps extends cdk.StackProps { projectName: string; githubOwnerName: string; githubRepositoryName: string; githubBranchName: string; githubTokenName: string; webhookSecretTokenName: string; } export class CodepipelineTriggeredByGithubReleasesStack extends cdk.Stack { constructor( scope: Construct, id: string, props: CodepipelineTriggeredByGithubReleaseStackProps ) { super(scope, id, props); const { projectName, githubOwnerName, githubRepositoryName, githubBranchName, githubTokenName, webhookSecretTokenName, } = props; const githubToken = cdk.SecretValue.secretsManager(githubTokenName).unsafeUnwrap(); const webhookSecretToken = cdk.SecretValue.secretsManager( webhookSecretTokenName ).unsafeUnwrap(); const sourceArtifact = new codePipeline.Artifact(); const codeBuildDeployProject = new codeBuild.PipelineProject( this, 'CodeBuildDeployProject', { projectName: `${projectName}-deploy-project`, buildSpec: codeBuild.BuildSpec.fromSourceFilename('./buildspec.yml'), } ); const sourceAction = new codePipelineActions.GitHubSourceAction({ actionName: 'source', owner: githubOwnerName, repo: githubRepositoryName, branch: githubBranchName, oauthToken: new cdk.SecretValue(githubToken), output: sourceArtifact, // デフォルトのトリガーを無効にする trigger: codePipelineActions.GitHubTrigger.NONE, }); const deployAction = new codePipelineActions.CodeBuildAction({ actionName: 'deploy', project: codeBuildDeployProject, input: sourceArtifact, }); const deployPipeline = new codePipeline.Pipeline(this, 'DeployPipeline', { pipelineName: `${projectName}-deploy-pipeline`, stages: [ { stageName: 'source', actions: [sourceAction], }, { stageName: 'deploy', actions: [deployAction], }, ], }); new codePipeline.CfnWebhook(this, 'WebhookResource', { authentication: 'GITHUB_HMAC', authenticationConfiguration: { secretToken: webhookSecretToken, }, // GitHub の Release イベントで送られてくるペイロードを通すためのフィルター filters: [ { jsonPath: '$.action', matchEquals: 'published', }, ], targetAction: sourceAction.actionProperties.actionName, targetPipeline: deployPipeline.pipelineName, targetPipelineVersion: 1, registerWithThirdParty: true, }); } }
GitHub で Release をトリガーに Webhook を投げる
上記の実装により、CodePipeline 側が GitHub の Release イベントで送られてくるペイロードを受け付けることができるようになりました。続いて、GitHub 側が Release をトリガーに Webhook を投げるための設定を行います。
先に上記のスタックを更新しておきます。
% cdk deploy "*"
下記 URL にアクセスして、対象となるリポジトリの Webhook 一覧画面を表示した後、「Edit」をクリックします。
https://github.com/[USER_NAME]/[REPOSITORY_NAME]/settings/hooks
「Let me select individual events.」を選択すると、イベントの一覧が表示されるので、
Pushes
のチェックを外して、代わりにReleases
にチェックを入れます。
「Update webhook」をクリックします。
Webhook 一覧画面に戻り、対象の Webhook に (release) と表示されていれば OK です。これで準備完了です。
デモ
試しに空コミットを作成して main ブランチに Push してみます。
% git commit --allow-empty -m "cicd test2" % git push origin main
すると、Push ではパイプラインが起動しなくなったことを確認できます(下記画像では source アクションの成功が44分前になっている)。
続いて、GitHub で Release をしてみます。下記 URL にアクセスして、対象となるリポジトリの Release 一覧を表示した後、「Create a new release」をクリックします。
https://github.com/[USER_NAME]/[REPOSITORY_NAME]/releases
「Choose a tag」で適当な名前のタグを作成します。
「Publish release」をクリックして、Release します。
すると、パイプラインが起動することを確認できます。
おわりに
GitHub の Release をトリガーにして、パイプラインを起動するためには少し煩雑な手順が必要でした。しかし、この手順を活用すれば、Release 以外のイベントについてもトリガーにすることが可能ですので、覚えておいて損は無いと思います。
近いうちに、GitHub のソースアクション「Version 2」を使用した場合の構成についても記事にしようと思うので、そちらも確認いただけると幸いです。
今回は以上になります。最後まで読んで頂きありがとうございました!